package com.peak.spring.boot.tools;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import com.peak.spring.boot.api.constant.ErrorMessage;
import com.peak.spring.boot.core.exception.Exceptions;
import com.peak.spring.boot.core.exception.ProjectException;

public class ZipFiles {

	/**
	 * 压缩文件为zip格式
	 * 
	 * @param srcDir
	 * @param response
	 */
	public static void zip(String srcDir, OutputStream response) {
		try (ZipOutputStream zos = new ZipOutputStream(response)) {
			File dir = new File(srcDir);
			if (!dir.exists()) {
				throw new ProjectException(ErrorMessage.FILE_NOT_FOUND);
			}
			zip(srcDir, dir, zos);
			zos.flush();
		} catch (Exception e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * 压缩文件为zip格式
	 * 
	 * @param srcPath
	 * @param zipFileName
	 */
	public static void zip(String srcPath, String zipFileName) {
		if (Emptys.isEmpty(srcPath) || Emptys.isEmpty(zipFileName)) {
			throw new ProjectException();
		}
		String zipPath = StringUtils.substringBeforeLast(zipFileName, File.separator);
		try {
			File srcFile = new File(srcPath);

			// 判断压缩文件保存的路径是否为源文件路径的子文件夹，如果是，则抛出异常（防止无限递归压缩的发生）
			if (srcFile.isDirectory() && zipPath.indexOf(srcPath) != -1) {
				throw new ProjectException();
			}

			// 判断压缩文件保存的路径是否存在，如果不存在，则创建目录
			File zipDir = new File(zipPath);
			if (!zipDir.exists() || !zipDir.isDirectory()) {
				zipDir.mkdirs();
			}

			// 创建压缩文件保存的文件对象
			String zipFilePath = zipPath + File.separator + zipFileName;
			File zipFile = new File(zipFilePath);
			if (zipFile.exists()) {
				// 检测文件是否允许删除，如果不允许删除，将会抛出SecurityException
				SecurityManager securityManager = new SecurityManager();
				securityManager.checkDelete(zipFilePath);
				// 删除已存在的目标文件
				zipFile.delete();
			}
			try (CheckedOutputStream cos = new CheckedOutputStream(new FileOutputStream(zipFile), new CRC32());
					ZipOutputStream zos = new ZipOutputStream(cos);) {
				// 如果只是压缩一个文件，则需要截取该文件的父目录
				String srcRootDir = srcPath;
				if (srcFile.isFile()) {
					int index = srcPath.lastIndexOf(File.separator);
					if (index != -1) {
						srcRootDir = srcPath.substring(0, index);
					}
				}
				// 调用递归压缩方法进行目录或文件压缩
				zip(srcRootDir, srcFile, zos);
				zos.flush();
			} catch (Exception e) {
				throw Exceptions.unchecked(e);
			}
		} catch (Exception e) {
			throw Exceptions.unchecked(e);
		}
	}

	/**
	 * 解压文件到某个目录下
	 * 
	 * @param zip
	 * @param unzipPath
	 */
	public static void unzip(File zip, String unzipPath) {
		ZipEntry entry = null;
		try (ZipInputStream zin = new ZipInputStream(new FileInputStream(zip)); ZipFile zipFile = new ZipFile(zip);) {
			// 注意，没加charset
			while ((entry = zin.getNextEntry()) != null) {
				File outFile = new File(unzipPath + File.separator + entry.getName());
				if (!entry.isDirectory()) {
					FileUtils.copyInputStreamToFile(zipFile.getInputStream(entry), outFile);
				}
			}
		} catch (IOException e) {
			throw new ProjectException(ErrorMessage.FILE_NOT_FOUND, e);
		}
	}

	private static void zip(String srcRootDir, File file, ZipOutputStream zos) throws Exception {
		if (file == null) {
			throw new ProjectException(ErrorMessage.FILE_NOT_FOUND);
		}
		// 如果是文件，则直接压缩该文件
		if (file.isFile()) {
			int count, bufferLen = 1024;
			byte data[] = new byte[bufferLen];

			// 获取文件相对于压缩文件夹根目录的子路径
			String subPath = file.getAbsolutePath();
			int index = subPath.indexOf(srcRootDir);
			if (index != -1) {
				subPath = subPath.substring(srcRootDir.length());
			}
			ZipEntry entry = new ZipEntry(subPath);
			zos.putNextEntry(entry);
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
			while ((count = bis.read(data, 0, bufferLen)) != -1) {
				zos.write(data, 0, count);
			}
			bis.close();
			zos.closeEntry();
		} else {
			// 如果是目录，则压缩整个目录
			// 压缩目录中的文件或子目录
			File[] listFiles = file.listFiles();
			if (Emptys.isNotEmpty(listFiles)) {
				for (File childFile : listFiles) {
					zip(srcRootDir, childFile, zos);
				}
			}
		}
	}
}
